﻿namespace SenenFernandez.Sensktop
{
    using SenenFernandez.Sensktop.Configuration;
    using SenenFernandez.Sensktop.Enum;
    using SenenFernandez.SmartExtensionMethods;
    using System;
    using System.Collections.Concurrent;
    using System.Diagnostics;
    using System.Timers;

    public class ProcessExplorer
    {
        private static readonly object sync = new object();
        private static ProcessExplorer instance;

        private readonly ConcurrentDictionary<Guid, Application> applications = new ConcurrentDictionary<Guid, Application>();

        private bool isInitialized;
        private Timer monitor;

        /// <summary>
        /// Singleton Instance.
        /// </summary>
        public static ProcessExplorer Instance
        {
            get
            {
                lock (sync)
                {
                    return instance ?? (instance = new ProcessExplorer());
                }
            }
        }

        /// <summary>
        /// Adds the application.
        /// </summary>
        public void Add(Application application)
        {
            applications.AddOrUpdate(application.Id, application, (key, value) => application);
            if (!isInitialized)
            {
                Initialize();
            }
        }

        /// <summary>
        /// Removes the application.
        /// </summary>
        public void Remove(Application application)
        {
            applications.TryRemove(application.Id, out application);
            Close(application);
            if (applications.Count == 0)
            {
                Uninitialize();
            }
        }

        /// <summary>
        /// Close all application.
        /// </summary>
        public void Shutdown()
        {
            try
            {
                lock (sync)
                {
                    foreach (var application in applications)
                    {
                        Remove(application.Value);
                    }
                    Uninitialize();
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }

        /// <summary>
        /// Starts new process.
        /// </summary>
        public Process StartNewProcess(Application application)
        {
            var newProcess = new Process
            {
                StartInfo =
                {
                    FileName = application.FileName,
                    Arguments = application.Arguments,
                    WindowStyle = ProcessWindowStyle.Maximized
                }
            };
            newProcess.Start();
            if (!application.IsConsole)
            {
                while (!newProcess.WaitForInputIdle(application.Timeout))
                {
                }
            }
            newProcess.Refresh();
            IntPtr handle;
            int retryCount = 0;
            while ((handle = newProcess.MainWindowHandle) == IntPtr.Zero && retryCount < application.RetryCount)
            {
                retryCount++;
                System.Threading.Thread.Sleep(application.RetryDelay);
                newProcess.Refresh();
            }
            handle.SetDecoration(WorkingArea.Instance.Area);
            return newProcess;
        }

        /// <summary>
        /// Checks all process.
        /// </summary>
        public void Check()
        {
            try
            {
                monitor.Stop();
                foreach (var application in applications)
                {
                    if (application.Value.Process != null)
                    {
                        if (!application.Value.Process.Responding)
                        {
                            if (!application.Value.Process.HasExited)
                            {
                                application.Value.SetNotRespondingCount(application.Value.NotRespondingCount + 1);
                                application.Value.SetApplicationStatus(ApplicationStatus.NotRespondig);
                                if (application.Value.NotRespondingCount > application.Value.MaxNotRespondingCount)
                                {
                                    KillProcess(application.Value.Process);
                                    if (application.Value.IsAutoRestart)
                                    {
                                        application.Value.StartProcess();
                                    }
                                }
                            }
                            else
                            {
                                Remove(application.Value);
                            }
                        }
                        else
                        {
                            application.Value.SetNotRespondingCount(0);
                            application.Value.SetApplicationStatus(ApplicationStatus.Running);
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
            finally
            {
                if (monitor != null)
                {
                    monitor.Start();
                }
            }
        }

        /// <summary>
        /// Resize applications on display settings changed.
        /// </summary>
        public void ResizeApplications()
        {
            try
            {
                foreach (var application in applications)
                {
                    if (application.Value.Process != null)
                    {
                        application.Value.Process.MainWindowHandle.SetDecoration(WorkingArea.Instance.Area);
                    }
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }

        private ProcessExplorer()
        {

        }

        private void Initialize()
        {
            try
            {
                lock (sync)
                {
                    if (!isInitialized)
                    {
                        Uninitialize();
                        monitor = new Timer(SensktopConfiguration.Instance.RefreshInterval);
                        monitor.Elapsed += OnElapsed;
                        monitor.Start();
                        isInitialized = true;
                    }
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }

        private void OnElapsed(object sender, ElapsedEventArgs e)
        {
            Check();
        }

        private void Close(Application application)
        {
            try
            {
                if (application != null)
                {
                    if (application.ProcessInstances != null)
                    {
                        foreach (var process in application.ProcessInstances)
                        {
                            CloseProcess(process.Value, application.Timeout);
                        }
                    }
                    CloseProcess(application.Process, application.Timeout);
                    application.ClearProcesses();
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }

        private void CloseProcess(Process process, int timeout)
        {
            if (!process.HasExited)
            {
                process.CloseMainWindow();
                if (!process.WaitForExit(timeout))
                {
                    KillProcess(process);
                }
            }
            process.Close();
            process.Dispose();
        }

        private void KillProcess(Process process)
        {
            process.Kill();
        }

        private void Uninitialize()
        {
            try
            {
                lock (sync)
                {
                    if (isInitialized)
                    {
                        if (monitor != null)
                        {
                            monitor.Stop();
                            monitor.Elapsed -= OnElapsed;
                            monitor.Dispose();
                            monitor = null;
                        }
                        if (isInitialized)
                        {
                            isInitialized = false;
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                exception.Log();
            }
        }
    }
}